package com.remoter.transport.socket.server;

import java.net.InetSocketAddress;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import com.remoter.basic.bean.support.ProviderModule;
import com.remoter.basic.bean.support.RemoterRequest;
import com.remoter.basic.bean.support.RemoterResponse;
import com.remoter.basic.transport.support.AbstractServer;
import com.remoter.basic.util.StringUtil;
import com.remoter.basic.util.ServiceLoader.MetaName;
import com.remoter.transport.socket.codec.MessageDecoder;
import com.remoter.transport.socket.codec.MessageEncoder;
import com.remoter.transport.socket.server.handler.ServerMessageHandler;
import com.remoter.transport.socket.util.FinalTransportSocket;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * @author			koko
 * @date 			2017-08-10 11:41:50
 * @description 	类功能说明
 */
@MetaName(FinalTransportSocket.D_VAL_SPI_NAME)
public class SocketServer extends AbstractServer{

	protected ConcurrentHashMap<String,ProviderModule> modules = new ConcurrentHashMap<String,ProviderModule>();
	
	protected InetSocketAddress local;
	protected NioEventLoopGroup work;
	protected NioEventLoopGroup boss;
	protected Channel channel;
	
	public SocketServer(){
		this.local = new InetSocketAddress(
			this.configuration.getOption(FinalTransportSocket.O_VAL_SERVER_HOST),
			this.configuration.getOption(FinalTransportSocket.O_VAL_SERVER_PORT)
		);
	}
	
	@Override
	public void bind(final InetSocketAddress local) throws Exception {
		if(null != local){
			this.local = local;
		}
		this.work = new NioEventLoopGroup();
		this.boss = new NioEventLoopGroup();
		ServerBootstrap bootstrap = new ServerBootstrap();
		bootstrap.group(this.boss,this.work);
		bootstrap.channel(NioServerSocketChannel.class);
		bootstrap.option(ChannelOption.SO_BACKLOG,128);
		bootstrap.option(ChannelOption.ALLOCATOR,PooledByteBufAllocator.DEFAULT);
		bootstrap.childOption(ChannelOption.ALLOCATOR,PooledByteBufAllocator.DEFAULT);
		bootstrap.childOption(ChannelOption.SO_KEEPALIVE,true);
		bootstrap.childHandler(new ChannelInitializer<SocketChannel>(){
			@Override
			protected void initChannel(SocketChannel socketChannel) throws Exception {
				ChannelPipeline channelPipeline = socketChannel.pipeline();
				channelPipeline.addLast(new MessageDecoder(serialization,RemoterRequest.class));
				channelPipeline.addLast(new MessageEncoder(serialization,RemoterResponse.class));
				channelPipeline.addLast(new ServerMessageHandler(provider));
			}
		});
		ChannelFuture channelFuture = bootstrap.bind(this.local);
		channelFuture.channel().closeFuture().addListener(new ChannelFutureListener(){
			@Override
			public void operationComplete(ChannelFuture future) throws Exception {
				logger.debug("provider disconnect " + local);
				unBind();
			}
		});
		channelFuture.addListener(new ChannelFutureListener(){
			@Override
			public void operationComplete(ChannelFuture future) throws Exception{
				if(future.isSuccess()){
					channel = future.channel();
					logger.info("start success ["+ local.toString() +"]!");
				}else{
					logger.info("start fail ["+ local.toString() +"]!");
				}
			}
		});
		channelFuture.sync();
	}

	@Override
	public void unBind() throws Exception {
		if(null != channel){
			channel.close();
		}
		if(null != this.work){
			this.work.shutdownGracefully().awaitUninterruptibly();
		}
		if(null != this.boss){
			this.boss.shutdownGracefully().awaitUninterruptibly();
		}
		for(Entry<String,ProviderModule> entry : this.modules.entrySet()){
			if(this.registry.detachProvider(entry.getValue())){
				this.modules.remove(entry.getKey());
				logger.debug("detach registry provider success for key : " + entry.getKey());
			}else{
				logger.warn("detach registry provider fail for key : " + entry.getKey());
			}
		}
	}

	@Override
	public boolean isAvailable() {
		if(null == channel){
			return false;
		}
		return channel.isOpen();
	}

	@Override
	public InetSocketAddress getLocalAddress() {
		return this.local;
	}

	@Override
	public void attachProvider(ProviderModule providerModule) {
		if(null == providerModule){
			logger.warn("provider module is null");
			return;
		}
		providerModule.setStartTime(System.currentTimeMillis());
		if(this.registry.attachProvider(providerModule)){
			this.modules.putIfAbsent(providerModule.getKey(),providerModule);
		}
	}

	@Override
	public void detachProvider(ProviderModule providerModule) {
		if(null == providerModule){
			logger.warn("provider module is null");
			return;
		}
		String key = providerModule.getKey();
		if(this.modules.containsKey(key) && this.registry.detachProvider(providerModule)){
			this.modules.remove(key);
		}
	}

	@Override
	public ProviderModule getProviderModule(RemoterRequest remoterRequest){
		for(Entry<String,ProviderModule> entry : this.modules.entrySet()){
			ProviderModule module = entry.getValue();
			if(
				StringUtil.isEquals(module.getType(), remoterRequest.getType()) && 
				StringUtil.isEquals(module.getBean(), remoterRequest.getBean())
			){
				return module;
			}
		}
		return null;
	}
	
}